/*
 * mtk.h -- header file of functions for mtktrum gsm module
 *
 * Copyright (C) 2007 lufang <lufang@gmail.com>
 *
 * Version 1.0 june 2007
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <signal.h>

#include "modemcontrol.h"
#include "gsm0710.h"
#include "buffer.h"
#include "utils.h"
#include "gsm_module.h"

#define MTK_MAX_FRAME_SIZE				256
#define MTK_MODULE_DEVICE_NAME 			"/dev/modem"

#define CMD_POWER_ON						"POWER_ON"
#define CMD_POWER_OFF						"POWER_OFF"
#define CMD_RESP_OK							"OK"

enum
{
	CHANNEL_VIRTUIAL,
	CHANNEL_DATA,
	CHANNEL_URC,
	CHANNEL_AT_COMMAND,
	CHANNEL_NR
};

typedef struct _PrivInfo
{
	char		init_urc[1024];
	int			urc_len;
}PrivInfo;

#define DECL_PRIV_INFO(i) PrivInfo* i = (PrivInfo*)thiz->priv;

static int mtk_module_startup (GsmModule* thiz);
static int mtk_extract_frames (GsmModule* thiz, GSM0710_Buffer* buf);
static int mtk_get_ussp_fd(GsmModule* thiz, int port);
static void mtk_reopen_ussp_fd(GsmModule* thiz, int port);

static void write_file(const char* name, const char* data, int data_len)
{
	FILE* fp = fopen(name, "ab+");
	
	if(fp != NULL)
	{
		fwrite(data, data_len, 1, fp);
		fclose(fp);
	}

	return;
}

static int mtk_real_write_uih_frame (GsmModule* thiz, int channel, const char* input, int count, unsigned char type)
{
	char buffer[MTK_MAX_FRAME_SIZE + 1] = {0x09, 0};
	int copy_size = 0;

	copy_size = min(count, MTK_MAX_FRAME_SIZE);
	memcpy (buffer + 1, input, copy_size);
	
	return generic_write_frame (thiz, channel, buffer, copy_size + 1, type) - 1;
}

static int mtk_real_write_frame (GsmModule* thiz, int channel, const char *input, int count, unsigned char type)
{
	int write_count = 0;
	
	if (type == UIH)
	{
		while (write_count < count)
		{
			write_count += mtk_real_write_uih_frame (thiz, channel, input + write_count, count - write_count, type);
		}

		return write_count;
	}
	else
	{
		return generic_write_frame(thiz, channel, input, count, type);
	}
}

static int mtk_write_frame (GsmModule* thiz, int channel, const unsigned char *input, int count, unsigned char type)
{
	if (type != UIH || channel == CHANNEL_VIRTUIAL)
	{
		return generic_write_frame (thiz, channel, (const char*)input, count, type);
	}

	if (channel == 1)
	{
		channel = CHANNEL_AT_COMMAND;
#ifdef ENABLE_LOG
		GSMMUX_DEBUG("GSMMUX:>CMD=%s\n", input);
#endif
	}
	else if (channel == 2)
	{
		channel = CHANNEL_DATA;
	}
	else if (channel == 3)
	{
		channel = CHANNEL_AT_COMMAND;
	}
	return mtk_real_write_frame (thiz, channel, (const char*)input, count, type);
}

static void mtk_module_param_negotiation (GsmModule* thiz)
{
	int i = 0;
	unsigned char param[] = { C_NP | CR, 0x11, 0x0, 0x10, 0x03, 0x0A, 0x00, 0x02, 0x03, 0x02};
	
	for (i = 0; i <= CHANNEL_AT_COMMAND; i++)
	{
		param[2] = i;
		mtk_write_frame(thiz, 0, param, 10, UIH);
		mtk_write_frame (thiz, i, NULL, 0, SABM | PF);
	}
		
	return;
}

static void mtk_modem_wait_ready (GsmModule* thiz)
{
	DECL_PRIV_INFO(info);
	int				fd = thiz->serial_fd;
	fd_set 			rfds;
	struct timeval 	timeout;
	unsigned char 	buf[1024] = {0};
	int				len = 0;
	int				sel = 0;
	const char*		p_start = NULL;
	
	memset (info->init_urc, 0, 1024);
	timeout.tv_sec = 10;
	timeout.tv_usec = 0;
	while (1)
	{
		FD_ZERO(&rfds);
		FD_SET(fd, &rfds);

		if ((sel = select(fd + 1, &rfds, NULL, NULL, &timeout)) > 0)
		{
			len = read(fd, buf, sizeof(buf));
			buf[len] = '\0';
			memcpy (info->init_urc + info->urc_len, buf, len);
			info->urc_len += len;
			if (findInBuf ((const unsigned char*)info->init_urc , info->urc_len, "+EIND: 1\r\n"))
			{
				break;
			}
		}
		else
		{
			break;
		}
		timeout.tv_sec = 5;
	}

	p_start = info->init_urc;
	for (len = 0; len < info->urc_len; len++)
	{
		if (p_start[len] == '+')
		{
			break;
		}
	}
	if (len < info->urc_len)
	{
		info->urc_len -= len;
		memmove(info->init_urc, info->init_urc + len, info->urc_len);
		info->init_urc[info->urc_len] = '\0';
	}
	else
	{
		info->urc_len = 0;
	}

	return;
}

static int mtk_module_open_mux(GsmModule* thiz)
{
	DECL_PRIV_INFO(info);
		char* 			mux_command = "AT+CMUX=0,0,115200,1024,10,3,30,10\r\n";
    
	info->urc_len = 0;
	mtk_modem_wait_ready (thiz);
	if (!SERIAL_AT_COMMAND(thiz, mux_command))
	{
		GSMMUX_PRINT("MUX mode doesn't function.\n");
	}
#ifdef ENABLE_LOG
		GSMMUX_DEBUG("info->init_urc = %s.\n", info->init_urc);
#endif

	return 0;
}

static int mtk_module_close (GsmModule* thiz)
{
	int fd = open(MTK_MODULE_DEVICE_NAME, O_RDWR);

	if(thiz->serial_fd > 0)
	{
		close_serial_port(thiz->serial_fd);
		thiz->serial_fd = 0;
	}
	
	ioctl(fd, MODEM_PWR_OFF);
	close (fd);
	GSMMUX_DEBUG("%s: \n", __func__);

	return 1;
}

static int mtk_module_destroy (GsmModule* thiz)
{
	int i = 0;

	pthread_kill(thiz->thread_id, SIGTERM);
	mtk_module_close (thiz);
	if(thiz->device_name != NULL)
	{
		free(thiz->device_name);
	}
	gsm0710_buffer_destroy(thiz->in_buf);

	for(i = 0; i < USSP_PORT_NR; i++)
	{
		close_pty(thiz->vir_ports.ussp_fds[i], i, thiz->vir_prefix);
		thiz->vir_ports.ussp_fds[i] = 0;
		free(thiz->vir_ports.ussp_name[i]);
		thiz->vir_ports.ussp_name[i] = NULL;
	}

	free(thiz);
	
	return 1;	
}

static void mtk_module_powon_device(GsmModule* thiz)
{
	if(thiz->serial_fd != 0)
	{
		close_serial_port(thiz->serial_fd);
		thiz->serial_fd = 0;
	}
#ifdef USE_HARDWARE
	int fd = 0;

	fd = open(MTK_MODULE_DEVICE_NAME, O_RDWR);
	
	ioctl(fd, MODEM_PWR_OFF);
	ioctl(fd, MODEM_PWR_ON);
	close (fd);

#else
	char*           reset_cmd = "AT+EPOF\r\n";
	unsigned char 	close_mux[3] = { C_CLD | CR, 1 , 0};

	if (!SERIAL_AT_COMMAND (thiz, "AT\r\n"))
	{
        GSMMUX_PRINT("Modem does not respond to AT commands, trying close MUX mode\n");
		mtk_write_frame(thiz, 0, close_mux, 2, UIH);
	}
	else
	{
		SERIAL_AT_COMMAND (thiz, reset_cmd);
	}
#endif

	sleep (1);
	thiz->serial_fd = open_serialport (thiz->device_name, 115200);

	return;
}

static void mtk_module_open_virtual_device(GsmModule* thiz)
{
	int i = 0;

	for(i = 0; i < USSP_PORT_NR; i++)
	{
		int name_len = 0;

		thiz->vir_ports.ussp_fds[i] = open_pty (i, thiz->vir_prefix);
		name_len = strlen(thiz->vir_prefix) + 5;
		thiz->vir_ports.ussp_name[i] = calloc(1, name_len);
		snprintf(thiz->vir_ports.ussp_name[i], name_len, "%s%d", thiz->vir_prefix, i);
	}

	return;
}

static void mtk_module_handle_ctrl_data (GsmModule* thiz)
{
	char    cmd[64] ={0};
	int     cmd_len = 0;
	int 	fd = mtk_get_ussp_fd(thiz, USSP_PORT_CTRL);

	GSMMUX_PRINT("%s: ", __func__);
	if ((cmd_len = read(fd, cmd, 64)) > 0)
	{
		GSMMUX_PRINT("mtk_module_handle_ctrl_data: cmd=%s\n", cmd);
		if (strncmp(cmd, CMD_POWER_ON, strlen(CMD_POWER_ON)) == 0)
		{
			mtk_module_startup(thiz);
			write(fd, CMD_RESP_OK, strlen(CMD_RESP_OK));
		}
		else if(strncmp(cmd, CMD_POWER_OFF, strlen(CMD_POWER_OFF)) == 0)
		{
			mtk_module_close(thiz);
			write(fd, CMD_RESP_OK, strlen(CMD_RESP_OK));
		}
	}
	else if(cmd_len == -1)
	{
		mtk_reopen_ussp_fd(thiz, USSP_PORT_CTRL);
		GSMMUX_PRINT("GSMMUX: error io, reopen ctrl port\n");
	}
	
	return;
}

static void PrintHex (register unsigned char * out, const unsigned char * in, int count)
{
	register unsigned char next_ch;
	static char hex[] = "0123456789ABCDEF";

	while (count-- > 0)
	{
		next_ch = *in++;
		*out++ = hex[(next_ch >> 4) & 0x0F];
		*out++ = hex[next_ch & 0x0F];        ++out;
	}
}

static void PrintChar (register unsigned char * out, const unsigned char * in, int count)
{
	register unsigned char next_ch;

	while (count-- > 0)
	{
		next_ch = *in++;
		if (next_ch < 0x20 || next_ch > 0x7e)
			*out++ = '.';
			else
			{
				*out++ = next_ch;
				if (next_ch == '%')   /* GSMMUX_PRINT/syslogd has a bug !! */
					*out++ = '%';
			}
	}

	*out = '\0';
}

static void PrintBuffer (const char *name, const unsigned char *buf, int count)
{
	unsigned char line[44*4]; 

	if (name != NULL)
		GSMMUX_PRINT("%s,n=%d\n", name, count);

		while (count > 6*4)
		{
			memset (line, 32, 44*4);
			PrintHex (line, buf, 6*4);
			PrintChar (&line[6 * 3*4], buf, 6*4);
			GSMMUX_PRINT("%s\n", line);
			count -= 6*4;
			buf += 6*4;
		}

		if (count > 0)
		{
			memset (line, 32, 44*4);
			PrintHex (line, buf, count);        PrintChar (&line[6 * 3*4], buf, count);
			GSMMUX_PRINT( "%s\n", line);
		}
}
static void mtk_module_handle_serial_data(GsmModule* thiz)
{
	unsigned char buf[1024] = {0};
	int		max_read = 0;
	int		bytes_read = 0;

	GSMMUX_PRINT("%s: ", __func__);
	max_read = min (gsm0710_buffer_free (thiz->in_buf), sizeof (buf));
	if ((bytes_read = read(thiz->serial_fd, buf, max_read)) > 0)
	{
		//write_file("/data/data/gsmmux_serial_read.bin", (const char*)buf, bytes_read);
#ifdef ENABLE_LOG
		GSMMUX_PRINT("########################mtk_module_handle_serial_data##################");
		PrintBuffer("gmmuxd: serial_data", buf, bytes_read);
#endif
		gsm0710_buffer_write (thiz->in_buf, (const char*)buf, bytes_read);
		mtk_extract_frames(thiz, thiz->in_buf);
	}

	return;
}

static void mtk_module_handle_ussp_data(GsmModule* thiz, int channel)
{
	unsigned char buf[1024] = {0};
	int		bytes_read = 0;

	GSMMUX_PRINT("%s: ", __func__);
	if((bytes_read = read(thiz->vir_ports.ussp_fds[channel], buf, sizeof(buf))) > 0)
	{
#ifdef ENABLE_LOG
		GSMMUX_PRINT("########################mtk_module_handle_ussp_data##################");
		PrintBuffer("gmmuxd: ussp_data", buf, bytes_read);
#endif
		mtk_write_frame(thiz, channel + 1, buf, bytes_read, UIH);
	}
	else if(bytes_read == -1)
	{
		thiz->vir_ports.ussp_fds[channel] = open_pty(channel, thiz->vir_prefix);
		GSMMUX_PRINT("GSMMUX: error io, reopen %d port\n", channel);
	}

	return;
}

static void* mtk_module_mainloop(void* param)
{
	GsmModule* thiz = (GsmModule*)param;
	
	mtk_module_open_virtual_device(thiz);
	
	while(1)
	{
		fd_set read_fds;
		int max_fd = 0;
		int i = 0;

		FD_ZERO(&read_fds);

		if(thiz->serial_fd > 0)
		{
			FD_SET (thiz->serial_fd, &read_fds);
			max_fd = thiz->serial_fd;
		//	GSMMUX_PRINT("%s: serial_fd=%d", __func__, thiz->serial_fd);
		}

		for (i = 0; i < USSP_PORT_NR; i++)
		{
		//	GSMMUX_PRINT("%s: ussp_fds[%d]=%d", __func__, i, thiz->vir_ports.ussp_fds[i]);
			FD_SET(thiz->vir_ports.ussp_fds[i], &read_fds);
			if (thiz->vir_ports.ussp_fds[i] > max_fd)
			{
				max_fd = thiz->vir_ports.ussp_fds[i];
			}
		}

		//GSMMUX_PRINT("%s: select", __func__);
		select(max_fd + 1, &read_fds, NULL, NULL, NULL);
		if(FD_ISSET(thiz->serial_fd, &read_fds))
		{
			mtk_module_handle_serial_data(thiz);
		} 
		else if(FD_ISSET(thiz->vir_ports.ussp_fds[USSP_PORT_CTRL], &read_fds))
		{
			mtk_module_handle_ctrl_data(thiz);
		}
		else
		{
			for (i = 0; i < USSP_PORT_NR - 1; i++)
			{
				if(FD_ISSET(thiz->vir_ports.ussp_fds[i], &read_fds))
				{
					mtk_module_handle_ussp_data(thiz, i);
					break;
				}
			}
		}
	}
	
	return NULL;
}

static int mtk_module_startup (GsmModule* thiz)
{
	DECL_PRIV_INFO(info);

	if(thiz->in_buf != NULL)
	{
		gsm0710_buffer_destroy(thiz->in_buf);
		thiz->in_buf = NULL;
	}
	thiz->in_buf = gsm0710_buffer_init(CHANNEL_AT_COMMAND, CHANNEL_URC, CHANNEL_DATA);
	mtk_module_powon_device(thiz);
	mtk_module_open_mux(thiz);
	mtk_module_param_negotiation(thiz);

	write(thiz->vir_ports.ussp_fds[0], info->init_urc, info->urc_len);

	return 1;
}

static int mtk_extract_frames (GsmModule* thiz, GSM0710_Buffer* buf)
{
	return generic_extract_frames (thiz, buf);
}

static int mtk_write_ussp_data (GsmModule* thiz, int channel, const unsigned char* input, int count)
{
	int wrote = 0;
	int	port = 0;

	if (channel == CHANNEL_URC || channel == CHANNEL_AT_COMMAND)
	{
		char* at = calloc(1, count);

		port = 0;
		memcpy(at, input + 1, count - 1);
#ifdef ENABLE_LOG
		GSMMUX_DEBUG("GSMMUX:< CMD = %s\n", at);
#endif
		free(at);
		if (findInBuf ((const unsigned char*)input+1, count - 1, "+EIND: 128"))
		{
			return count;
		}
	}
	else
	{
		port = 1;
	}

	wrote = write(thiz->vir_ports.ussp_fds[port], input + 1, count - 1);

/*
	if(port == CHANNEL_DATA)
	{
		GSMMUX_PRINT("##########################Write data ##############################");
		PrintBuffer("data: ", input + 1, count - 1);
	}
*/
	return count;
}

static int mtk_get_ussp_fd(GsmModule* thiz, int port)
{
	return thiz->vir_ports.ussp_fds[port];
}

static void mtk_reopen_ussp_fd(GsmModule* thiz, int port)
{
	thiz->vir_ports.ussp_fds[port] = open_pty(port, thiz->vir_prefix);

	return;
}

static GsmModule* mtk_module_create (GsmModuleGenerator* generator, const char* device_name)
{
	pthread_attr_t attr;
	GsmModule* thiz = calloc(1, sizeof (GsmModule));
	PrivInfo* info = calloc(1, sizeof(PrivInfo));

	thiz->module_destroy = mtk_module_destroy;
	thiz->write_ussp_data = mtk_write_ussp_data;

	thiz->device_name = strdup(device_name);
	thiz->vir_prefix = "/dev/mtk";
	thiz->priv = info;

	/*create module listen thread*/
	pthread_attr_init (&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	pthread_create(&thiz->thread_id, &attr, mtk_module_mainloop, thiz);

	rename("/data/data/gsmmux_serial_read.bin", "/data/data/gsmmux_serial_read.bin.last");
	rename("/data/data/gsmmux_serial_write.bin", "/data/data/gsmmux_serial_write.bin.last");

	return thiz;
}

static int mtk_generator_match_type (GsmModuleGenerator* generator, const char* type)
{
	return (strcmp (type, "mtk") == 0);
}

static void mtk_generator_destroy (GsmModuleGenerator* generator)
{
	free(generator);
	
	return;
}

GsmModuleGenerator* mtk_get_generator ()
{
	GsmModuleGenerator* generator = calloc(1, sizeof (GsmModuleGenerator));

	generator->create_module_func = mtk_module_create;
	generator->match_func = mtk_generator_match_type;
	generator->destroy_func = mtk_generator_destroy;

	return generator;
}


